package org.ns.framework.datasource.config;


import org.aopalliance.intercept.MethodInvocation;
import org.ns.basics.commons.lang.RandomStringUtils;
import org.ns.basics.commons.lang.StringUtils;
import org.ns.framework.extension.aop.AopMethodInterceptor;
import org.ns.framework.extension.aop.AopProxy;
import org.ns.framework.extension.aop.ProxyPointcut;
import org.ns.framework.webmvc.error.resulterror.ApiFrequentAccessException;
import org.springframework.beans.factory.InitializingBean;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Transaction;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;

/*
   @Bean("xxRedis")
   @ConfigurationProperties(prefix="qxiu.redis.base")
   Redis xxRedis(){
    return new Redis();
   }

   @Resource(name="xxRedis")
   Redis xxRedis;
   Jedis jedis = xxRedis.getJedis();
   jedis.close();
 */
public class Redis implements InitializingBean {
	/** redis 连接地址 */
	private String host;
	/** redis 端口 */
	private int port;
	/** 数据库的选择 */
	private int database;
	/** 密码, 没有可以不写 */
	private String password;
	/** 连接超时时间 */
	private int timeout;
	/** jedis的配置 */
	private RedisConfig config = new RedisConfig();

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public int getDatabase() {
		return database;
	}

	public void setDatabase(int database) {
		this.database = database;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		if (StringUtils.isBlank(password)) {
			password = null;
		}
		this.password = password;
	}

	public int getTimeout() {
		return timeout;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	public RedisConfig getConfig() {
		return config;
	}

	public void setConfig(RedisConfig config) {
		this.config = config;
	}

	private JedisPool jedisPool;
	private Object lock = new Object();

	@Override
	public void afterPropertiesSet() throws Exception {
		createJedisPool();
	}

	public void createJedisPool() {
		synchronized (lock) {
			if (jedisPool == null || jedisPool.isClosed()) {
				jedisPool = new JedisPool(config, host, port, timeout, password, database);
			}
		}
	}

	/**
	 * 得到 Jedis, 使用完 jedis.close() <BR>
	 * 时间：Feb 27, 2019 1:25:00 PM
	 *
	 * @return
	 */
	public Jedis getJedis() {
		createJedisPool();
		return jedisPool.getResource();
	}

	/**
	 * 不建议， 直接使用 jedis.close即可。 <BR>
	 * 时间：Feb 27, 2019 1:23:59 PM
	 *
	 * @param jedis
	 */
	public void free(Jedis jedis) {
		jedisPool.returnResource(jedis);
	}

	public JedisPool getJedisPool() {
		createJedisPool();
		return jedisPool;
	}

	private final static Jedis AOPJEDIS = new Jedis(); // 做切面的原始对象
	private Jedis jedisProxy = null; // 代理的jedis
	public static Set<String> excludeJedisCmds = new HashSet<String>(
			Arrays.asList("close", "multi", "pipelined", "multi", "flushAll", "flushDB"));

	/**
	 * 获得这个代理 <BR>
	 * 时间：Feb 28, 2019 11:49:20 AM
	 *
	 * @return
	 */
	public Jedis getJedisProxy() {
		if (jedisProxy == null) {
			jedisProxy = AopProxy.proxy(AOPJEDIS, new ProxyPointcut(null, null), new JedisProxyInterceptor());
		}
		return jedisProxy;
	}
	
	/**
	 * 执行redis的业务， 回调结束自动释放连接
	 * <BR>
	 * 时间：May 14, 2020 4:46:56 PM
	 *
	 * @param redisExec
	 * @return 结束可以携带返回值
	 */
	public <R> R execute(Function<Jedis, R> redisExec) {
		try (Jedis jedis = getJedis();) {
			R result = redisExec.apply(jedis);
			return result;
		}
	}
	
	/**
	 * 执行redis的业务， 回调结束自动释放连接
	 * <BR>
	 * 时间：May 14, 2020 4:46:56 PM
	 *
	 * @param redisExec
	 */
	public void execute(Consumer<Jedis> redisExec) {
		try (Jedis jedis = getJedis();) {
			redisExec.accept(jedis);
		}
	}
	
	/**
	 * 通过代理的Jedis方式执行。执行一条释放一条
	 * <BR>
	 * 时间：May 14, 2020 4:44:59 PM
	 *
	 * @param redisExec
	 * @return 结束可以携带返回值
	 */
	public <R> R executeProxy(Function<Jedis, R> redisExec) {
		try (Jedis jedis = getJedisProxy();) {
			R result = redisExec.apply(jedis);
			return result;
		}
	}
	
	/**
	 * 通过代理的Jedis方式执行。执行一条释放一条
	 * <BR>
	 * 时间：May 14, 2020 4:44:59 PM
	 *
	 * @param redisExec
	 */
	public void executeProxy(Consumer<Jedis> redisExec) {
		try (Jedis jedis = getJedisProxy();) {
			redisExec.accept(jedis);
		}
	}
	
	/**
	 * 事物执行，自动释放
	 * <BR>
	 * 时间：May 14, 2020 6:09:47 PM
	 *
	 * @param transExec
	 * @return
	 */
	public List<Object> transExec(Consumer<Transaction> transExec) {
		try (Jedis jedis = getJedis();) {
			Transaction transaction = jedis.multi();
			transExec.accept(transaction);
			return transaction.exec();
		}
	}
	
	/**
	 * 原子执行、当没有争抢到锁到时候，发生 ApiFreightOperationException 异常，请开发者自行捕获！
	 * <BR>
	 * 时间：May 15, 2020 2:18:19 PM
	 *
	 * @param key 锁key
	 * @param second 持有锁最长的时间.
	 * @param exec
	 * @return
	 */
	public void atomicExec(String key, int second, NoRtnAtomicExec exec) throws ApiFrequentAccessException {
		atomicExec(key, second, ()->{
			exec.exec();
			return null;
		});
	}
	/**
	 * 原子执行、当没有争抢到锁到时候，发生 ApiFreightOperationException 异常，请开发者自行捕获！
	 * <BR>
	 * 时间：May 15, 2020 2:18:19 PM
	 *
	 * @param key
	 * @param second
	 * @param exec
	 * @return
	 * @throws ApiFrequentAccessException
	 */
	public Object atomicExec(String key, int second, AtomicExec exec) throws ApiFrequentAccessException {
		Jedis jedis = getJedisProxy();
		String value = RandomStringUtils.random(10, "0123456789");
		try {
			key = key + ".ATLOCK";
			if (jedis.set(key, value, "NX", "EX", second) == null) {
				throw new ApiFrequentAccessException("访问过于繁忙");
			}
			return exec.exec();
		} catch (Exception e) {
			throw e;
		} finally {
			String recodeValue = jedis.get(key);
			if (value.equals(recodeValue)) {
				jedis.del(key);
			}
		}
			
	}
	
	@FunctionalInterface
	public static interface AtomicExec {
		Object exec();
	}
	@FunctionalInterface
	public static interface NoRtnAtomicExec {
		void exec();
	}
	
	private class JedisProxyInterceptor implements AopMethodInterceptor {
		
		// private Map<Jedis, Boolean> noFreejedis = new ConcurrentHashMap<>();
		ThreadLocal<Map<Jedis, Boolean>> threadLocal = new ThreadLocal<>();
		
		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			Method method = invocation.getMethod();
			String name = invocation.getMethod().getName();
			if (Transaction.class.isAssignableFrom(method.getReturnType())) {
				Jedis jedis = getJedis();
				putNoFreeJedis(jedis);
				Object result = invocation.getMethod().invoke(jedis, invocation.getArguments());
				Transaction transaction = (Transaction)result;
				return AopProxy.proxy(transaction, new ProxyPointcut(null, null), new MultiTransaction(jedis, transaction, this));
			} else if (Pipeline.class.isAssignableFrom(method.getReturnType())) {
				Jedis jedis = getJedis();
				putNoFreeJedis(jedis);
				Object result = invocation.getMethod().invoke(jedis, invocation.getArguments());
				Pipeline pipeline = (Pipeline)result;
				return AopProxy.proxy(pipeline, new ProxyPointcut(null, null), new PipelineInvocation(jedis, pipeline, this));
			} else if (name.equals("close")) {
				Map<Jedis, Boolean> noFreejedis = threadLocal.get();
				if (noFreejedis != null) { // 当前线程存在为释放的JEDIS则执行
					for (Jedis jedis : new HashSet<>(noFreejedis.keySet())) {
						try {
							noFreejedis.remove(jedis);
							jedis.close();
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
					threadLocal.remove(); // 移除当前线程的实例
				}
				return null;
			}
			if (excludeJedisCmds.contains(name)) {
				throw new RuntimeException("不支持当前方法:" + name);
			}
			try (Jedis jedis = getJedis()){
				return invocation.getMethod().invoke(jedis, invocation.getArguments());
			} catch (Exception e) {
				throw e;
			}
		}
		public void putNoFreeJedis(Jedis jedis) {
			// 获取当前线程的MAP
			Map<Jedis, Boolean> map = threadLocal.get();
			if (map == null) {
				map = new ConcurrentHashMap<Jedis, Boolean>();
				threadLocal.set(map);
			}
			// PUT进去
			map.put(jedis, true);
		}
		public void removeFree(Jedis jedis) {
			if (jedis == null) {
				return;
			}
			// 获取当前线程的MAP
			Map<Jedis, Boolean> noFreejedis = threadLocal.get();
			try {
				if (noFreejedis != null) { // 存在则移除当前JEDIS
					noFreejedis.remove(jedis);
				}
				jedis.close(); // 关闭
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	private class MultiTransaction implements AopMethodInterceptor {
		private Jedis jedis = null;
		private Transaction transaction;
		private JedisProxyInterceptor jedisProxyInterceptor;
		public MultiTransaction(Jedis jedis, Transaction transaction, JedisProxyInterceptor jedisProxyInterceptor) {
			this.jedis = jedis;
			this.transaction = transaction;
			this.jedisProxyInterceptor = jedisProxyInterceptor;
		}
		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			Method method = invocation.getMethod();
			Object[] args = invocation.getArguments();
			String name = method.getName();
			try {
				Object result = method.invoke(transaction, args);
				return result;
			} catch (Throwable e) {
				throw e;
			} finally {
				if (name.startsWith("exec")) {
					jedisProxyInterceptor.removeFree(jedis);
				}
			}
		}
	}
	private class PipelineInvocation implements AopMethodInterceptor {
		private Jedis jedis = null;
		private Pipeline pipeline;
		private JedisProxyInterceptor jedisProxyInterceptor;
		public PipelineInvocation(Jedis jedis, Pipeline pipeline, JedisProxyInterceptor jedisProxyInterceptor) {
			this.jedis = jedis;
			this.pipeline = pipeline;
			this.jedisProxyInterceptor = jedisProxyInterceptor;
		}
		@Override
		public Object invoke(MethodInvocation invocation) throws Throwable {
			Method method = invocation.getMethod();
			Object[] args = invocation.getArguments();
			String name = method.getName();
			try {
				Object result = method.invoke(pipeline, args);
				return result;
			} catch (Throwable e) {
				throw e;
			} finally {
				if (name.startsWith("sync")) {
					jedisProxyInterceptor.removeFree(jedis);
				}
			}
		}
	}
}
